package com.raccoonagents.tools.cascet2d.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;

import org.lwjgl.LWJGLException;
import org.lwjgl.devil.IL;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.glu.Disk;

import com.raccoonagents.tools.cascet2d.interfaces.rendering.IMaskedTexturedRenderable;
import com.raccoonagents.tools.cascet2d.interfaces.rendering.IRenderableObject;
import com.raccoonagents.tools.cascet2d.interfaces.rendering.ITexturedRenderable;
import com.raccoonagents.tools.cascet2d.model.Vector2f;
import com.raccoonagents.tools.cascet2d.model.exceptions.FontBuildException;
/**
 * 
 * @author Kari Kuvaja
 *
 */
public class GraphicUtil {
	
	/**
	 * 
	 * @author Kari Kuvaja
	 *	supported image formats
	 */
	public static enum IMAGE_TYPE{
		GIF(IL.IL_GIF,".gif"),
		JPG(IL.IL_JPG,".jpg"),
		BMP(IL.IL_BMP,".bmp"),
		PNG(IL.IL_PNG,".png");
		
		private final String extension;
		private final int type;
		private IMAGE_TYPE(final int type, final String extension){
			this.type = type;
			this.extension = extension;
		}
		
		public static IMAGE_TYPE getTypeFromName(final String path){
			for(final IMAGE_TYPE type : values()){
				if(path.toLowerCase().endsWith(type.extension)){
					return type;
				}
			}
			return null;
		}

		public int getType() {
			return type;
		}
		
	}	
	
	public static void renderMaskedImage(final IMaskedTexturedRenderable r, final int image, final int mask){
		// GL11.glLoadIdentity();
		final float x = r.getLocation().getX();
		final float y = r.getLocation().getY();
		final float sizex = r.getSize().x;
		final float sizey = r.getSize().y;
		
		
		GL11.glPushMatrix();
		GL11.glTranslatef(r.getLocation().getX(), r.getLocation().getY(), 0.0f);
		GL11.glRotatef(r.getRotation(), 0.0f,0.0f, 1.0f);
		GL11.glTranslatef(-r.getSize().x * r.getPivot().x, -r.getSize().y * r.getPivot().y, 0.0f);		
		// final Vector2f size = r.getSize();
		GL11.glEnable(GL11.GL_TEXTURE_2D);
		GL11.glEnable(GL11.GL_BLEND);			
		
		
		GL11.glDisable(GL11.GL_DEPTH_TEST);
		
		// render the image mask 
		GL11.glBlendFunc(GL11.GL_DST_COLOR,GL11.GL_ZERO);		
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, mask);
		GL11.glColor4f(1.0f, 1.0f, 1.0f, r.getOpacity());		
		GL11.glBegin(GL11.GL_QUADS);
		
		GL11.glTexCoord2f(0.0f, 1.0f);
		GL11.glVertex2f(0.0f,sizey);
		
		GL11.glTexCoord2f(0.0f, 0.0f);		
		GL11.glVertex2f(0.0f,0.0f);		
		
		GL11.glTexCoord2f(1.0f, 0.0f);
		GL11.glVertex2f(sizex,0.0f);	
		
		GL11.glTexCoord2f(1.0f, 1.0f);
		GL11.glVertex2f(sizex,sizey);	
		
		GL11.glEnd();		
		// end of render image mask
		
		//render the image 
		
		GL11.glBlendFunc(GL11.GL_ONE,GL11.GL_ONE);		
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, image);
		GL11.glColor4f(1.0f, 1.0f, 1.0f, r.getOpacity());		
		GL11.glBegin(GL11.GL_QUADS);
		GL11.glTexCoord2f(0.0f, 1.0f);
		GL11.glVertex2f(0.0f,sizey);
		
		GL11.glTexCoord2f(0.0f, 0.0f);		
		GL11.glVertex2f(0.0f,0.0f);		
		
		GL11.glTexCoord2f(1.0f, 0.0f);
		GL11.glVertex2f(sizex,0.0f);	
		
		GL11.glTexCoord2f(1.0f, 1.0f);
		GL11.glVertex2f(sizex,sizey);	
		GL11.glEnd();
		// end of render image		
		
		GL11.glPopMatrix();
		
	}
	
	public static void renderMaskedImage(final IMaskedTexturedRenderable r){
		renderMaskedImage(r, r.getTexture(), r.getMaskImage());
	}

	
	
	
	
	public static void renderImage(final ITexturedRenderable r, final int image){
		final Vector2f size = r.getSize();
		// GL11.glLoadIdentity();
		GL11.glPushMatrix();
		
//		final Vector2f position = r.getLocation();
		
		GL11.glEnable(GL11.GL_TEXTURE_2D);
		GL11.glDisable(GL11.GL_DEPTH_TEST);
		GL11.glTranslatef(r.getLocation().getX(), r.getLocation().getY(), 0.0f);
		GL11.glRotatef(r.getRotation(), 0.0f,0.0f, 1.0f);
		GL11.glTranslatef(-r.getSize().x * r.getPivot().x, -r.getSize().y * r.getPivot().y, 0.0f);		
		
		//
		GL11.glEnable(GL11.GL_BLEND);
		GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
		GL11.glEnable(GL11.GL_COLOR_MATERIAL);
	    GL11.glColorMaterial ( GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE ) ;
		
		//GL11.glDisable(GL11.GL_COLOR_MATERIAL);
		GL11.glColor4f(r.getColor()[0], r.getColor()[1], r.getColor()[2],r.getOpacity());

		GL11.glBindTexture(GL11.GL_TEXTURE_2D, image);
		
		// start quad..
		GL11.glBegin(GL11.GL_QUADS);		
		
		GL11.glTexCoord2f(0.0f, 1.0f); GL11.glVertex2f(0.0f,size.y);		
		GL11.glTexCoord2f(0.0f, 0.0f); GL11.glVertex2f(0.0f,0.0f);				
		GL11.glTexCoord2f(1.0f, 0.0f); GL11.glVertex2f(size.x,0.0f);			
		GL11.glTexCoord2f(1.0f, 1.0f); GL11.glVertex2f(size.x,size.y);	
						
		GL11.glEnd();
		// done, pops!
		GL11.glPopMatrix();
		
	}
	
	public static void renderRectangleWireframe(final IRenderableObject r){
		renderRectangleWireframe(r.getLocation(), r.getSize(),r.getColor(), r.getOpacity(), r.getPivot(), r.getRotation());
	}
	
	/**
	 * TODO: rotation
	 * @param position
	 * @param size
	 * @param color
	 * @param opacity
	 */
	public static void renderRectangleWireframe(final Vector2f position, final Vector2f size, final float [] color, final float opacity, final Vector2f pivot, float angle){
		GL11.glPushMatrix();
		float xcoord = position.getX() - size.x * pivot.x;
		float ycoord = position.getY() - size.y * pivot.y;
		
		GL11.glTranslatef(xcoord, ycoord, 0.0f);
		GL11.glRotatef(angle, 0.0f,0.0f, 1.0f);
		GL11.glTranslatef(-size.x * pivot.x, -size.y * pivot.y, 0.0f);		

		GL11.glDisable(GL11.GL_TEXTURE_2D);
		GL11.glDisable(GL11.GL_DEPTH_TEST);
		
		GL11.glEnable(GL11.GL_BLEND);			
		GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
		GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK,GL11.GL_LINE);
		GL11.glColor4f(color[0], color[1], color[2], opacity);
		GL11.glBegin(GL11.GL_QUADS);
		GL11.glVertex2f(0.0f,0.0f);
		GL11.glVertex2f(size.x,0.0f);
		GL11.glVertex2f(size.x,size.y);
		GL11.glVertex2f(0,size.y);
		GL11.glEnd();
		GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
		GL11.glDisable(GL11.GL_BLEND);
		GL11.glPopMatrix();
		
	}
	
	public static void renderImage(final ITexturedRenderable r){
		renderImage(r,r.getTexture());
	}
	
	/**
	 * Determine an available display that matches the specified parameters.
	 * 
	 * @param size
	 *            The desired width of the screen
	 * @param size
	 *            The desired height of the screen
	 * @param bpp
	 *            The desired color depth (bits per pixel) of the screen
	 * @return The display mode matching the requirements or null if none could
	 *         be found
	 * @throws LWJGLException
	 *             library.
	 */
	public static DisplayMode findDisplayMode(final int width, final int height, final int bpp) throws LWJGLException {
		final DisplayMode[] modes = Display.getAvailableDisplayModes();
		DisplayMode mode = null;

		for (int i = 0; i < modes.length; i++) {
			if ((modes[i].getBitsPerPixel() == bpp) || (mode == null)) {
				if ((modes[i].getWidth() == width) && (modes[i].getHeight() == height)) {
					mode = modes[i];
				}
			}
		}

		return mode;
	}		
	
	public static void renderRectangle(final IRenderableObject r){
		final Vector2f size = r.getSize();
		GL11.glPushMatrix();


		GL11.glTranslatef(r.getLocation().x, r.getLocation().y,0);
		GL11.glRotatef(r.getRotation(), 0.0f,0.0f,1.0f);
		GL11.glTranslatef(-r.getSize().x * r.getPivot().x, -r.getSize().y * r.getPivot().y, 0.0f);		

		
		
		GL11.glDisable(GL11.GL_TEXTURE_2D);
		GL11.glEnable(GL11.GL_BLEND);
		GL11.glDisable(GL11.GL_DEPTH_TEST);
		GL11.glDisable(GL11.GL_COLOR_MATERIAL);
		GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
		GL11.glColor4f(r.getColor()[0], r.getColor()[1], r.getColor()[2],r.getOpacity());

		GL11.glBegin(GL11.GL_QUADS);
		GL11.glVertex2f(0.0f,0.0f);
		GL11.glVertex2f(size.x,0.0f);
		GL11.glVertex2f(size.x,size.y);
		GL11.glVertex2f(0,size.y);
		GL11.glEnd();
		
		GL11.glPopMatrix();
		
	}
	
	public static void renderVector(final Vector2f origin, final Vector2f vectorTo){
		GL11.glBegin(GL11.GL_LINES);
		GL11.glVertex2f(origin.x, origin.y);
		GL11.glVertex2f(vectorTo.x, vectorTo.y);
		GL11.glEnd();		
	}
	/**
	 * TODO: rotation
	 * @param position
	 * @param size
	 * @param opacity
	 * @param color
	 * @param pivot
	 */
	public static void renderRectangle(final Vector2f position, final Vector2f size, final float opacity, final float [] color, final Vector2f pivot){
		GL11.glPushMatrix();
		GL11.glTranslatef(position.getX() - size.x * pivot.x, position.getY() - size.y * pivot.y, 0.0f);
		GL11.glDisable(GL11.GL_TEXTURE_2D);
		GL11.glDisable(GL11.GL_DEPTH_TEST);
		
		GL11.glEnable(GL11.GL_BLEND);			
		GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
	
		GL11.glColor4f(color[0], color[1], color[2], opacity);
		GL11.glBegin(GL11.GL_QUADS);
		GL11.glVertex2f(0.0f,0.0f);
		GL11.glVertex2f(size.x,0.0f);
		GL11.glVertex2f(size.x,size.y);
		GL11.glVertex2f(0,size.y);
		GL11.glEnd();
		
		GL11.glDisable(GL11.GL_BLEND);
		GL11.glPopMatrix();
		
	}
	
	public static void renderSphere(final IRenderableObject r){
		GL11.glPushMatrix();
		GL11.glDisable(GL11.GL_TEXTURE_2D);
		GL11.glEnable(GL11.GL_BLEND);
		GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
		//GL11.glTranslatef(r.getLocation().getX() - r.getSize().x * r.getPivot().x, r.getLocation().getY() - r.getSize().y * r.getPivot().y, 0.0f);
		GL11.glTranslatef(r.getLocation().getX(), r.getLocation().getY(), 0.0f);
		final Disk disk = new Disk();
		GL11.glColor4f(r.getColor()[0],r.getColor()[1], r.getColor()[2],r.getOpacity());
		disk.draw(r.getSize().x,0.0f, 16, 16);		
		GL11.glPopMatrix();

	}
	
	public static void renderBG(final int image, final int width, final int height){
		GL11.glPushMatrix();
		GL11.glEnable(GL11.GL_TEXTURE_2D);
		GL11.glDisable(GL11.GL_BLEND);
		GL11.glDisable(GL11.GL_DEPTH_TEST);
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, image);
		
		GL11.glDisable(GL11.GL_COLOR_MATERIAL);
		GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
		GL11.glBegin(GL11.GL_QUADS);		

		
		GL11.glTexCoord2f(0.0f, 1.0f);
		GL11.glVertex2f(0.0f,height);
		
		GL11.glTexCoord2f(0.0f, 0.0f);		
		GL11.glVertex2f(0.0f,0.0f);		
		
		GL11.glTexCoord2f(1.0f, 0.0f);
		GL11.glVertex2f(width,0.0f);	
		
		GL11.glTexCoord2f(1.0f, 1.0f);
		GL11.glVertex2f(width,height);	
		
		GL11.glEnd();
		
		GL11.glPopMatrix();
		
	}
	
	public static void renderBG(final int image) {
		//GL11.glLoadIdentity();
		
		GL11.glPushMatrix();
		GL11.glEnable(GL11.GL_TEXTURE_2D);
		GL11.glDisable(GL11.GL_BLEND);
		GL11.glDisable(GL11.GL_DEPTH_TEST);
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, image);
		
		GL11.glDisable(GL11.GL_COLOR_MATERIAL);
		GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
		final int height = Display.getDisplayMode().getHeight();
		final int width = Display.getDisplayMode().getWidth();
		GL11.glBegin(GL11.GL_QUADS);		

		
		GL11.glTexCoord2f(0.0f, 1.0f);
		GL11.glVertex2f(0.0f,height);
		
		GL11.glTexCoord2f(0.0f, 0.0f);		
		GL11.glVertex2f(0.0f,0.0f);		
		
		GL11.glTexCoord2f(1.0f, 0.0f);
		GL11.glVertex2f(width,0.0f);	
		
		GL11.glTexCoord2f(1.0f, 1.0f);
		GL11.glVertex2f(width,height);	
		
		GL11.glEnd();
		
		GL11.glPopMatrix();
	}


	public static void initGL() {

		GL11.glMatrixMode(GL11.GL_PROJECTION);
		GL11.glLoadIdentity(); // Reset The Projection Matrix
		org.lwjgl.opengl.glu.Project.gluLookAt(0,0,1f,0, 0, -1f, 0, 1, 0);
		
		GL11.glViewport(0, 0, (int) (Display.getDisplayMode().getWidth() * 1.0f), (int) (Display.getDisplayMode().getHeight() * 1.0f));
		GL11.glOrtho(0, Display.getDisplayMode().getWidth(), 0, Display.getDisplayMode().getHeight(), 1, -1);
		
		GL11.glMatrixMode(GL11.GL_MODELVIEW); // Select The Modelview Matrix
		GL11.glLoadIdentity(); // Reset The Projection Matrix
		// Really Nice Perspective Calculations
		GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST);
		GL11.glShadeModel(GL11.GL_SMOOTH); // Enable Smooth Shading
		GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Black Background
		GL11.glClearDepth(1.0f); // Depth Buffer Setup
		GL11.glEnable(GL11.GL_DEPTH_TEST); // Enables Depth Testing
		GL11.glDepthFunc(GL11.GL_LEQUAL); // The Type Of Depth Testing
	}

	
	public static int loadTexture(final String path) throws IOException{
		final FileInputStream fis = new FileInputStream(new File(path));
		final int type = IMAGE_TYPE.getTypeFromName(path).getType();
		return loadTexture(fis, type);
		
	}
	
	
	/**
	 * @param is : the stream to image.
	 * @param type : IL.IL_GIF, IL.IL_JPG ....
	 * @throws IOException 
	 */
	private static int loadTexture(final InputStream is, final int type) throws IOException {
		
		final IntBuffer image = ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder()).asIntBuffer();
		IL.ilGenImages(image);
		IL.ilBindImage(image.get(0));

		IL.ilLoadFromStream(is, type);
		// IL.ilLoadImage(path);
		IL.ilConvertImage(IL.IL_RGBA, IL.IL_BYTE);
		final ByteBuffer scratch = ByteBuffer.allocateDirect(IL.ilGetInteger(IL.IL_IMAGE_WIDTH) * IL.ilGetInteger(IL.IL_IMAGE_HEIGHT) * 4);
		IL.ilCopyPixels(0, 0, 0, IL.ilGetInteger(IL.IL_IMAGE_WIDTH), IL.ilGetInteger(IL.IL_IMAGE_HEIGHT), 1, IL.IL_RGBA, IL.IL_BYTE, scratch);

		// Create A IntBuffer For Image Address In Memory
		final IntBuffer buf = ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder()).asIntBuffer();
		GL11.glGenTextures(buf); // Create Texture In OpenGL

		GL11.glBindTexture(GL11.GL_TEXTURE_2D, buf.get(0));
		// Typical Texture Generation Using Data From The Image

		// Linear Filtering
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
		// Linear Filtering
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
		// Generate The Texture
		GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, IL.ilGetInteger(IL.IL_IMAGE_WIDTH), IL.ilGetInteger(IL.IL_IMAGE_HEIGHT), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, scratch);
		is.close();
		return buf.get(0); // Return Image Address In Memory
	}
	
	public static void doGameWindow(final int width, final int height, final String title, final String font, final boolean fullScreen) throws LWJGLException, FontBuildException{
			// find out what the current bits per pixel of the desktop is
			final int currentBpp = Display.getDisplayMode().getBitsPerPixel();

			final DisplayMode mode = GraphicUtil.findDisplayMode(width,height, currentBpp);

			// if can't find a mode, notify the user the give up
			if (mode == null) {
				//Sys.alert("Error", "800x600" + currentBpp + " display mode unavailable");
				//return;
				throw new LWJGLException("Display mode " + width+"x"+height+" is not available");
			}
			
			// configure and create the LWJGL display
			Display.setTitle(title);

			Display.setFullscreen(fullScreen);
			
			// create the main display
			Display.create();
			Display.setDisplayMode(mode);
			
			IL.create();
			FontUtils.init(font);
			
			IL.ilOriginFunc(IL.IL_ORIGIN_LOWER_LEFT);
			IL.ilEnable(IL.IL_ORIGIN_SET);
			
	}	
}
